<template>
  <div class="graph-root">
    <!--  画布容器 可滚动  -->
    <div ref="graphContent" class="graph-cont-scroll">
      <!--   缩放   -->
      <zoom-control class="zoom-control" @scale="changeScale" />
      <!--   画布   -->
      <div
        id="global-graph"
        ref="graph"
        class="graph dragging-to"
        :style="{
          width: `${graphSize.width - 6}px`,
          height: `${graphSize.height - 6}px`,
        }"
        @mousedown="chooseBoxStart($event)"
      >
        <!-- 算子 节点 -->
        <template v-for="(node, index) in operatorNodes">
          <!-- 清洗节点菜单 -->
          <div
            v-if="node.type === 5"
            :key="'cleannode' + index"
            :style="{
              left: `${
                calcXPositionByCol(node.data.position.col) -
                (11 * zoomScale) / 100
              }px`,
              top: `${
                calcYPositionByRow(node.data.position.row) - nodeHeight
              }px`,
              height: `${cleanMenuHeight}px`,
              width: `${cleanMenuWidth}px`,
            }"
            class="clean-node-menu"
            :class="{
              opacity40:
                locateNodeArray.length > 0
                  ? !calcMultiSelectNodeArray(locateNodeArray, node.id)
                  : false,
              'clean-node-menu-forbidden': node.data.forbidden,
            }"
          >
            <div class="icon-list">
              <div
                v-for="(item, i) in combineCleanActions(node.data.cleanActions)
                  .length > 3
                  ? combineCleanActions(node.data.cleanActions).slice(0, 4)
                  : combineCleanActions(node.data.cleanActions)"
                :key="`${item.actionType}${i}`"
                class="icon"
                :style="{ 'font-size': `${(zoomScale * 13) / 100}px` }"
              >
                <a-popover
                  v-if="i !== 3"
                  placement="top"
                  :overlay-style="{ width: 'auto!important' }"
                >
                  <div slot="content">
                    <p
                      v-for="(action, j) in item.recordList"
                      :key="`${action.actionType}${j}`"
                      style="
                        font-size: 12px;
                        margin-bottom: 0;
                        text-overflow: ellipsis;
                        white-space: nowrap;
                      "
                    >
                      {{
                        `${
                          ActionInCN[item.actionType]
                            ? ActionInCN[item.actionType]
                            : item.label
                        }`
                      }}{{
                        action.data.col === undefined
                          ? ''
                          : `【${action.data.col}】`
                      }}
                    </p>
                  </div>
                  <div class="icon-with-status">
                    <a-icon-font
                      v-if="item.status !== 'SUCCESS'"
                      class="status-icon"
                      :style="{
                        'font-size': `${(zoomScale * 12) / 100}px`,
                        'margin-bottom': `${(zoomScale * 2) / 100}px`,
                      }"
                      type="iconweiwanpeizhi"
                    />
                    <a-icon-font
                      class="operate-icon"
                      :style="{ padding: `${(zoomScale * 2) / 100}px` }"
                      :type="
                        filterActionIconType[item.actionType] ||
                        'iconqingxi-copy'
                      "
                    />
                  </div>
                </a-popover>
                <a-popover
                  v-else
                  placement="topRight"
                  :overlay-style="{ width: 'auto!important' }"
                >
                  <div slot="content">
                    <!-- <p style="
                      font-size: 12px;
                      margin-bottom: 0;
                      text-overflow: ellipsis;
                      white-space: nowrap;"
                      v-for="(action,j) in item.recordList" :key="`${action.actionType}${j}`">
                      {{`${ActionInCN[item.actionType]? ActionInCN[item.actionType]: item.label}`}}{{action.data.col===undefined?'':`【${action.data.col}】`}}
                      </p> -->
                    <div
                      v-for="(moreItem, i) in combineCleanActions(
                        node.data.cleanActions
                      ).slice(3)"
                      :key="`${moreItem.actionType}${i}+4`"
                    >
                      <div
                        v-for="(moreAction, j) in moreItem.recordList"
                        :key="`${moreAction.actionType}${j}`"
                      >
                        <a-icon-font
                          v-if="moreItem.status === 'SUCCESS' && j === 0"
                          :type="
                            filterActionIconType[moreItem.actionType] ||
                            'iconqingxi-copy'
                          "
                        />
                        <span
                          class="m-span"
                          :style="{
                            color:
                              moreAction.data.status !== 'SUCCESS'
                                ? 'red'
                                : 'rgba(0, 0, 0, 0.65)',
                            'font-size': '12px',
                            'margin-bottom': 0,
                            'margin-left': 5,
                            'text-overflow': 'ellipsis',
                            'white-space': 'nowrap',
                            paddingLeft: j > 0 ? '18px' : 0,
                          }"
                        >
                          {{
                            `${
                              ActionInCN[moreItem.actionType]
                                ? ActionInCN[moreItem.actionType]
                                : moreItem.label
                            }`
                          }}{{
                            moreAction.data.col === undefined
                              ? ''
                              : `【${moreAction.data.col}】`
                          }}</span
                        >
                      </div>
                    </div>
                  </div>
                  <div class="icon-with-status">
                    <a-icon-font
                      class="operate-icon"
                      type="icongengduocaozuo"
                    />
                  </div>
                </a-popover>
                <!-- <div v-else class="icon-with-status">
                    <a-icon-font  class="operate-icon" type="icongengduocaozuo"
                      @mouseover="mouseOver(node)" @mouseleave="mounseLeave" />
                  </div> -->
              </div>
            </div>
            <div
              v-if="combineCleanActions(node.data.cleanActions).length > 0"
              class="bottom-div"
              :style="{
                width: `${(zoomScale * 60) / 100}px`,
                height: `${(zoomScale * 4) / 100}px`,
              }"
            ></div>
            <!-- <div class="more-icon-menu"
              :style="{
                left: `${nodeWidth-200}px`,
                top: `${-30}px`,
                display: moreHover===node.id?'inline-block':'none'
              }"
            >
              <div
                v-for="(moreItem, i) in combineCleanActions(
                  node.data.cleanActions
                ).slice(3)"
                :key="`${moreItem.actionType}${i}+4`"
              >
                <div
                  v-for="(action, j) in moreItem.recordList"
                  :key="`${action.actionType}${j}`"
                >
                  <a-icon-font
                    v-if="moreItem.status === 'SUCCESS' && j === 0"
                    :type="
                      filterActionIconType[moreItem.actionType] ||
                      'iconqingxi-copy'
                    "
                  />
                  <span
                    class="m-span"
                    :style="{
                      color: action.data.status!=='SUCCESS'?'red':'rgba(0, 0, 0, 0.65)',
                      paddingLeft:j>0?'18px':0}">
                    {{`${ActionInCN[moreItem.actionType]}  【${action.data.col}】`}}</span>
                  </div>


                  <a-icon-font v-if="moreItem.status === 'SUCCESS'" :type="filterActionIconType[moreItem.actionType] || 'iconqingxi-copy'" />
                  <span v-for="(action,j) in moreItem.recordList" :key="`${action.actionType}${j}`" class="m-span"
                  :style="{
                    color: action.data.status!=='SUCCESS'?'red':'rgba(0, 0, 0, 0.65)',
                    paddingLeft:j>0?'18px':0}">
                  {{`${ActionInCN[moreItem.actionType]}  【${action.data.col}】`}}</span>
                </div>
              </div> -->
          </div>
          <!-- 节点 -->
          <div
            :key="'operator' + index"
            :class="{
              'node-selected': selectedNode
                ? selectedNode.id === node.id
                : false,
              'node-box-choose':
                multiSelectNodeArray.length > 0
                  ? calcMultiSelectNodeArray(multiSelectNodeArray, node.id)
                  : false,
              'node-located':
                locateNodeArray.length > 0
                  ? calcMultiSelectNodeArray(locateNodeArray, node.id)
                  : false,
              'node-background-running': getNodeStatus(node.id) === 'RUNNING',
              opacity40:
                locateNodeArray.length > 0
                  ? !calcMultiSelectNodeArray(locateNodeArray, node.id)
                  : false,
            }"
            class="node-capsule"
            :style="{
              left: `${calcXPositionByCol(node.data.position.col)}px`,
              top: `${calcYPositionByRow(node.data.position.row)}px`,
              width: `${nodeWidth}px`,
              height: `${nodeHeight}px`,
            }"
            @mousedown="startDragging($event, node)"
            @click="$event.stopPropagation()"
          >
            <!-- TODO 附属节点 'z-index': node.data.parametersTable ? 60 - node.data.parametersTable.index : 60-->
            <!-- 节点胶囊 -->
            <node-capsule
              :node="node"
              :zoom-scale="zoomScale"
              :node-height="nodeHeight"
              :node-width="nodeWidth"
              :grid-size-width="gridSizeWidth"
              :grid-size-height="gridSizeHeight"
              :difference-of-grid-width="differenceOfGridWidth"
              :difference-of-grid-height="differenceOfGridHeight"
              :node-status="getNodeStatusTip(node.id)"
              :mode="isVisitor || isLock"
              :menu-open-id="menuOpenNodeId"
              :comment="node.data.comments ? node.data.comments.content : ''"
              @select-node="selectNode"
              @dblclick-node="selectCircleNode"
              @node-copy="nodeCopy"
              @node-event="nodeEmitEvent"
              @delete-node="deleteNode"
              @node-edit="nodeEdit(node)"
              @update-pipeline="updatePipeline"
            >
              <!-- 具名插槽 折叠附属节点 TODO 附属节点 && !node.data.nodeOfAttached -->
              <template #toggleNode>
                <div
                  v-if="!comparisonStatus"
                  class="node-hover-toggle-table"
                  @click="toggleAttachedNode($event, node.id)"
                >
                  <a-icon-font
                    class="type-icon-color"
                    :type="
                      toggleNodeId === node.id
                        ? 'iconjuleisuanzishouqi-putongzhuangtai'
                        : 'iconjuleisuanzixiala-putongzhuangtai'
                    "
                  />
                </div>
              </template>

              <!--  具名插槽 添加按钮 有正在编辑的就不再显示   -->
              <template #add>
                <div
                  v-if="
                    !comparisonStatus &&
                    (!editCurrentNode ||
                      (editCurrentNode && editCurrentNode.id === node.id)) &&
                    node.type !== 6 &&
                    !isVisitor &&
                    !isLock
                  "
                  class="add-node-button after-add-node"
                  @click="editAdditionalNode(node, $event)"
                >
                  <add-menu
                    :node-id="node.id"
                    :node-position="node.data.position"
                    :visible="menuOpenNodeId === null && clickedAdd"
                    @add-node="nodeAddNode"
                  />
                </div>
              </template>
            </node-capsule>
          </div>
        </template>

        <!--   连接线编辑 menu  -->
        <div
          v-if="edgeMenuInfo.show && !isVisitor && !isLock"
          :style="{
            position: 'absolute',
            left: edgeMenuInfo.x + 'px',
            top: edgeMenuInfo.y + 'px',
            'z-index': 70,
            width: '124px',
          }"
          class="menu-container"
        >
          <a-menu class="a-menu-container">
            <a-menu-item class="a-menu-cell" @click="deleteConnectLine()"
              >删除
            </a-menu-item>
          </a-menu>
        </div>

        <!-- 连接线 @menu-position="menuPositionEmit" -->
        <line-of-relational-connection
          v-for="edge in getEdgeInfo"
          :key="edge.key"
          :edge="edge"
          :node-height="nodeHeight"
          :node-width="nodeWidth"
          :difference-of-grid-width="differenceOfGridWidth"
          :difference-of-grid-height="differenceOfGridHeight"
          :highlight="(connectionLineHighlight.indexOf(`${edge.startNodeId}-${edge.endNodeId}`) > -1)"
          :line-status="lineStatus(edge.endNodeId)"
          :is-visitor="isVisitor"
          :is-lock="isLock"
          @click-add-node="clickAddNode"
          @active-line="activeLine"
          @edit-line="EmitEditLine"
        />

        <!-- 连线添加节点 menu  -->
        <line-add-menu
          v-if="btnStatus"
          :style="{
            left: lineAddMenuPosition.x + 'px',
            top: lineAddMenuPosition.y + 'px',
          }"
          :line-add-node-menu-status="lineAddNodeMenuStatus"
          @line-add-node="relationLineAddNode"
        />

        <!-- 框选 框 -->
        <div
          v-show="chooseBoxShow"
          class="box-to-choose"
          :style="{
            height:
              Math.abs(
                chooseBoxStartEndPosition.end.y -
                  chooseBoxStartEndPosition.start.y
              ) + 'px',
            width:
              Math.abs(
                chooseBoxStartEndPosition.end.x -
                  chooseBoxStartEndPosition.start.x
              ) + 'px',
            left: positionX + 'px',
            top: positionY + 'px',
            'z-index': 100,
          }"
        ></div>

        <!-- 网格示意落点 -->
        <drag-target-position
          v-if="gridDraggingShow"
          :style="{
            left: `${(nodeGridPosition.col - 1) * gridSizeWidth}px`,
            top: `${(nodeGridPosition.row - 1) * gridSizeHeight}px`,
            width: `${nodeGridSize.colCount * gridSizeWidth}px`,
            height: `${nodeGridSize.rowCount * gridSizeHeight}px`,
          }"
          :node-grid-size="nodeGridSize"
          :node-height="nodeHeight"
          :node-width="nodeWidth"
          :grid-size-height="gridSizeHeight"
          :grid-size-width="gridSizeWidth"
          :difference-of-grid-width="differenceOfGridWidth"
          :difference-of-grid-height="differenceOfGridHeight"
        />

        <!-- 拖拽虚拟节点 -->
        <template v-for="(vNode, index) in draggingNodeItem">
          <div
            v-show="dragging"
            ref="dragging"
            :key="index + 'vnode'"
            class="dragging-node"
            :style="{
              left: `${vNode.data.position.x}px`,
              top: `${vNode.data.position.y}px`,
              width: `${nodeWidth}px`,
              height: `${nodeHeight}px`,
            }"
          >
            <virtual-node-capsule
              :node="draggingNodeItem[index]"
              :zoom-scale="zoomScale"
              :node-height="nodeHeight"
              :node-width="nodeWidth"
            />
          </div>
        </template>

        <!-- 连接关系编辑 -->
        <div
          v-show="nodeRelevanceShow"
          class="node-relevance"
          :style="{
            left: `${(nodeRelevancePosition.col - 1) * gridSizeWidth}px`,
            top: `${(nodeRelevancePosition.row - 1) * gridSizeHeight}px`,
            width: `${gridSizeWidth}px`,
            height: `${gridSizeHeight}px`,
          }"
        >
          <div
            v-show="acceptNodeInput"
            class="relevance-top relevance-fun"
            :style="{
              width: `${nodeWidth}px`,
              height: `${nodeHeight - 10}px`,
              'line-height': `${nodeHeight - 10}px`,
              left: differenceOfGridWidth + 'px',
              bottom: differenceOfGridHeight + nodeHeight + 4 + 'px',
            }"
            @mouseup="nodeConnection"
          >
            <a-icon-font type="iconzengjia" class="type-icon-color" />
            连接
          </div>
          <div
            v-show="panelDragOperator"
            class="relevance-bottom relevance-fun"
            :style="{
              width: `${nodeWidth}px`,
              height: `${nodeHeight - 10}px`,
              'line-height': `${nodeHeight - 10}px`,
              left: differenceOfGridWidth + 'px',
              top: differenceOfGridHeight + nodeHeight + 2 + 'px',
            }"
            @mouseup="nodeUnionJoin(4)"
          >
            <a-icon-font type="iconlianhe" />
            UNION
          </div>
          <div
            v-show="panelDragOperator"
            class="relevance-right relevance-fun"
            :style="{
              width: `${differenceOfGridWidth}px`,
              height: `${nodeHeight - 10}px`,
              'line-height': `${nodeHeight - 10}px`,
              left: differenceOfGridWidth + nodeWidth + 4 + 'px',
              top: differenceOfGridHeight + 'px',
            }"
            @mouseup="nodeUnionJoin(2)"
          >
            <a-icon-font type="iconlianjie" />
            JOIN
          </div>
        </div>
      </div>
    </div>
    <!-- 刪除节点确认 todo -->
    <node-delete-confirm
      :visible="deleteConfirmVisible"
      :nodes="deleteNodes"
      @confirm="confirmDeleteNode"
      @cancel="cancelDelete"
    />

    <!-- popup -->
    <!-- 编辑自定义算子 type === 4 -->
    <operator-editor
      :operator-id="operatorId"
      :task-id="taskId"
      :visible="addEditVisible"
      @close-pop="addEditVisible = false"
    />
  </div>
</template>

<script lang="ts">
import Component from 'vue-class-component'
import { Vue, Prop, Watch } from 'vue-property-decorator'
import { cloneDeep, merge, toInteger } from 'lodash'
// 缩放
import ZoomControl from '@/components/studio/data/graph-compoments/ZoomControl.vue'
// 连线
import LineOfRelationalConnection from '@/components/studio/data/graph-compoments/LineOfRelationalConnection.vue'
// 节点胶囊
import NodeCapsule from '@/components/studio/data/graph-compoments/NodeCapsule.vue'
// 虚拟节点
import VirtualNodeCapsule from '@/components/studio/data/graph-compoments/VirtualNodeCapsule.vue'
// 添加节点按钮
import AddMenu from '@/components/studio/data/graph-compoments/AddMenu.vue'
// 连接线添加节点
import LineAddMenu from '@/components/studio/data/graph-compoments/LineAddMenu.vue'
// 节点删除确认
import NodeDeleteConfirm from '@/components/studio/data/NodeDeleteConfirm.vue'
// 拖拽网格
import DragTargetPosition from '@/components/studio/data/graph-compoments/DragTargetPosition.vue'
import DragTargetPositionCircle from '@/components/studio/data/graph-compoments/DragTargetPositionCircle.vue'
import DataViewStore, { EditActionArea } from '@/store/modules/dataview'
import GraphViewStore from '@/store/modules/graphview'
import {
  // StatisticsAction,
  ActionInCN,
  filterActionIconType, // 清洗操作的图标
} from '@/util/data-selection-actions'
//  编辑自定义算子
import OperatorEditor from '@/components/studio/data/popup/OperatorEditor.vue'
// import Canvg from 'canvg'

import {
  batchDelete,
  batchForbidden,
  batchUnForbidden,
  deleteLineById,
  batchCopy,
  pipelineInstanceStatus,
  simpleUpdate,
  taskAddUnionJoinNode,
  taskComplexLoad,
  taskConnectLine,
} from '@/api/task'
// jupyter 自定义算子
import { queryJlabURL } from '@/api/jupyter'
// 工具方法 getNodeById,
import {
  acceptNodeInputOrNot,
  ComputeSpaceAfterNode,
  calcNodeGridSize,
  generateEdgeInfo,
  getNodeByPosition,
  getParentSonPosition,
  recursionDealLine,
  calcGridShow,
  gridAvaliable,
  // saveCanvasToImage
} from '@/components/studio/data/util-pipeline'

import {
  Node,
  NodeGridSize,
  Position,
  PositionWithColRow,
  Size,
} from '@/components/studio/data/interface'
import { pipelineExecute, pipelineStop } from '@/api/pipeline'
import UserGuideStore from '@/store/modules/user-guide'

interface KeyValue {
  [key: string]: any
}

@Component({
  components: {
    ZoomControl,
    LineOfRelationalConnection,
    NodeCapsule,
    AddMenu,
    DragTargetPosition,
    DragTargetPositionCircle,
    LineAddMenu,
    VirtualNodeCapsule,
    NodeDeleteConfirm,
    OperatorEditor,
  },
})
export default class Pipeline extends Vue {
  /**
   * 常量尺寸
   */
  // 节点尺寸 （init 基础尺寸）
  private nodeHeight: number = 40
  private initNodeHeight: number = 40
  private nodeWidth: number = 58
  private initNodeWidth: number = 58

  // 清洗菜单尺寸
  private cleanMenuHeight: number = 40
  private initMenuHeight: number = 40
  private cleanMenuWidth: number = 80
  private initMenuWidth: number = 80

  // 网格尺寸 （init 基础尺寸）
  private gridSizeWidth: number = 168
  private initGridSizeWidth: number = 168
  private gridSizeHeight: number = 128
  private initGridSizeHeight: number = 128
  // 初始比例系数
  public zoomScale: number = 100
  // more icon menu 样式
  public moreHover: any = 0
  // actionType 中文
  private ActionInCN = ActionInCN
  // cleannode hover id
  private cleanNodeId: any = undefined
  // 网格是否能放置节点
  private nodeMap: any = {}
  // 当前右键menu打开节点id
  public menuOpenNodeId: number | null = null
  // 点击add按钮
  public clickedAdd: boolean = false
  // 项目 ID
  @Prop({ type: Number, default: 0 })
  projectId!: number
  // pipelineId 沿用版本1
  @Prop({ type: Number, default: 0 })
  pipelineId!: number

  // 显示编辑算子 type === 4
  public addEditVisible: boolean = false
  //  自定义算子operatorId
  public operatorId: number = 0

  public get editOperatorParam() {
    return DataViewStore.EditOperatorParam
  }

  public get isLock() {
    return DataViewStore.getLock
  }

  public get pipelineDeleteConfirm() {
    return DataViewStore.pipelineDeleteConfirm
  }

  /**
   * 编辑算子
   * @param newValue
   */
  @Watch('editOperatorParam', { deep: true })
  public changeEditOperatorModal(newValue: any) {
    // 新的自定义算子 jupyter type === 9
    if (newValue.type === 9) {
      this.queryJupyterOperator(newValue)
      return
    }
    if (newValue.operatorId) {
      this.operatorId = newValue.operatorId
      this.taskId = newValue.taskId
      this.addEditVisible = true
    }
  }

  /**
   * 获取该自定义节点的信息, 打开新窗口（两个来源）
   * @param info
   */
  public async queryJupyterOperator(info: any) {
    this.taskId = info.taskId
    const parameters = {
      projectId: this.projectId,
      id: info.taskId,
    }
    await queryJlabURL({ data: parameters })?.then((response: any) => {
      if (response.data.code === 100) {
        window.open(response.data.result, '_blank')
      } else {
        this.$message.error('请求失败！')
      }
    })
  }

  // 自定义算子 taskId
  public taskId: number = 0

  /**
   * pipeline 传值监听, pipeline 传入 跟新 pipeline
   */
  @Watch('pipelineId')
  public changePipelineId() {
    this.updatePipeline()
  }

  private async created() {
    await DataViewStore.getPipelineDeleteConfirm()
  }

  public get isVisitor() {
    return DataViewStore.isVisitor
  }

  public get pipelineSnapshotId() {
    return DataViewStore.pipelineSnapshotId
  }

  /**
   * 清洗节点
   */
  private get filterActionIconType() {
    return filterActionIconType
  }

  /**
   * 清洗操作合并
   */
  private combineCleanActions(actions: any) {
    const map: KeyValue = {}
    const resultArray: {
      actionType: any
      recordList: any[]
      status: string
      label: string
    }[] = []
    actions.slice(1).forEach(
      (action: {
        data: {
          actionType: string
          action: string
          status: string
          label: string
        }
      }) => {
        if (!map[action.data.actionType]) {
          resultArray.push({
            actionType: action.data.actionType,
            recordList: [action],
            status: action.data.status,
            label: action.data.label,
          })
          map[action.data.actionType] = action
        } else {
          resultArray.forEach((item) => {
            if (item.actionType === action.data.actionType) {
              item.recordList.push(action)
            }
          })
        }
      }
    )
    return resultArray
  }

  private cleanNodeActionsKeys(node: any) {
    const keys = Object.keys(node.data.cleanActions)
    if (keys.length > 3) {
      return keys.slice(0, 3)
    }
    return keys
  }

  private cleanActionsGeneral(actions: any) {
    actions.slice(0)
    // let errorMessage = ''
    // for (const item of actions){
    //   if (item.data.status !== 'SUCCESS'){
    //     errorMessage += JSON.parse(item.data.logInfo).error_msg
    //   }
    // }
    // if (errorMessage){
    //   return errorMessage
    // }
    return 'SUCCESS'
  }

  public mouseOver(node: any) {
    this.moreHover = node.id
  }

  public mounseLeave() {
    this.moreHover = 0
  }

  /**
   * 画布的实际节点
   */
  // 选择的节点
  public get selectedNode() {
    return DataViewStore.selectNode
  }

  // 选择的节点
  public get editNode() {
    return DataViewStore.editOperatorNode
  }

  // 节点 列表
  private get operatorNodes() {
    return DataViewStore.pipelineNodes
  }

  // 监听 pipeline 节点变化,
  @Watch('operatorNodes', { deep: true })
  public updatePipelineNodes() {
    this.initGraphSizeByNodes()
    // 计算网格是否能放置节点（连线上不能放置节点-横线）
    const { nodeWidth, nodeHeight, gridSizeWidth, gridSizeHeight } = this
    this.nodeMap = gridAvaliable(this.getEdgeInfo, {
      nodeWidth,
      nodeHeight,
      gridSizeWidth,
      gridSizeHeight,
    })
    if (this.selectedNode) {
      const node = this.operatorNodes.find((item) => {
        return item.id === this.selectedNode.id
      })
      DataViewStore.setSelectNode(node)
    }
  }

  @Watch('pipelineSnapshotId')
  public excute() {
    this.runPipeline(true)
  }

  /**
   * 画布容器
   */
  // 画布容器 位置 初始化获取
  public containerPos: Position = { x: 0, y: 0 }
  // 画布容器 宽高  初始化获取
  public containerHeightWidth: Size = { width: 0, height: 0 }
  // 画布容器对象
  private graphContent: any = null
  /**
   * 画布
   */
  // 画布尺寸
  private graphSize: Size = { width: 0, height: 0 }
  // 画布
  private graphElement: any = null

  /**
   * 虚拟节点
   */
  // 当前拖拽的节点  复制  虚拟节点
  public draggingNodeItem: Array<any> = []
  // 拖拽 标志
  public dragging = false

  /**
   * 拖拽示意位置
   */
  // 拖拽预设位置， 移动条件是否成立（位置是否合法）
  private gridDraggingShow: boolean = false
  // 网格 尺寸
  private nodeGridSize: NodeGridSize = { rowCount: 0, colCount: 0 }
  // 网格 定位
  private nodeGridPosition: PositionWithColRow = { col: -1, row: -1 }
  // 拖拽节点的拖拽 范围
  private colParentMax: number | null = 100
  private colChildMin: number | null = 0

  /**
   * deep 监听 节点网格示意位置变化
   */
  @Watch('nodeGridPosition', { deep: true })
  private watchNodeGridPosition(position: any) {
    if (this.draggingNodeItem.length > 0) {
      const ranksOfLimit = {
        colParentMax: this.colParentMax,
        colChildMin: this.colChildMin,
      }
      // this.gridDraggingShow = this.calcGridShow(position.col, position.row, this.draggingNodeItem)
      // TODO this.dragging 问题
      if (
        Object.values(this.nodeMap)
          .flat(200)
          .includes(`${position.row}-${position.col}`)
      ) {
        // 子节点可以在自己与父节点的线上移动
        if (
          !Object.keys(this.nodeMap)
            .flat(200)
            .includes(this.draggingNodeItem[0].id.toString()) ||
          this.nodeMap[this.draggingNodeItem[0].id]?.length < 1
        ) {
          this.gridDraggingShow = false
          return
        }
        if (
          this.draggingNodeItem[0].childId.split(',') &&
          this.draggingNodeItem[0].parentId.split(',').length < 2
        ) {
          // 子节点线上 父节点可放置
          this.gridDraggingShow = true
          return
        }
      }
      this.gridDraggingShow =
        calcGridShow(
          position,
          ranksOfLimit,
          this.nodeGridSize,
          this.gridStartDragPosition,
          this.operatorNodes,
          this.draggingNodeItem
        ) && this.dragging
    }
  }

  /**
   * 关系编辑
   */

  // 被连接节点
  private connectedNode: any = null
  // 关系连接 按钮显示
  private nodeRelevanceShow: boolean = false
  // 关系编辑 定位
  private nodeRelevancePosition: PositionWithColRow = { col: -1, row: -1 }
  // 目标节点可以接受此节点为输入时显示
  private acceptNodeInput: boolean = false

  /**
   * deep 监听 节点网格示意位置变化, 关系 关联
   */
  @Watch('nodeRelevancePosition', { deep: true })
  private watchNodeRelevancePosition(value: PositionWithColRow) {
    // todo 优化 是否 panel 拖拽 编辑节点关系
    // 根据位置 获取该位置的节点信息 todo 不使用 Watch
    this.calcNodeRelevancePosition(value.col, value.row)
    // this.connectedNode = getNodeByPosition(this.operatorNodes, value.col, value.row)
    // const dragNode = this.draggingNodeItem[0]
    // // 拖拽一个节点的时候
    // this.nodeRelevanceShow = this.draggingNodeItem.length === 1 && this.connectedNode && dragNode.id !== this.connectedNode.id
    // // 目标节点是否可以接受此节点为输入
    // this.acceptNodeInput = acceptNodeInputOrNot(this.connectedNode)
  }

  /**
   * 关系编辑位置变化
   */
  public calcNodeRelevancePosition(col: number, row: number) {
    this.connectedNode = getNodeByPosition(this.operatorNodes, col, row)
    const dragNode = this.draggingNodeItem[0]
    // 拖拽一个节点的时候
    this.nodeRelevanceShow =
      this.draggingNodeItem.length === 1 &&
      this.connectedNode &&
      dragNode.id !== this.connectedNode.id &&
      !this.isVisitor &&
      !this.isLock
    // 目标节点是否可以接受此节点为输入
    this.acceptNodeInput =
      acceptNodeInputOrNot(this.connectedNode) && dragNode?.type !== 6
    this.panelDragOperator =
      this.connectedNode?.type !== 6 &&
      this.draggingNodeItem.length === 1 &&
      dragNode?.type !== 6
  }

  // 节点与网格 相对位置差  X
  public get differenceOfGridWidth() {
    return (this.gridSizeWidth - this.nodeWidth) / 2
  }

  // 节点与网格 相对位置差  Y
  public get differenceOfGridHeight() {
    return (this.gridSizeHeight - this.nodeHeight) / 2
  }

  public get actionArea() {
    return DataViewStore.ActionArea
  }

  // node 节点点击 click 和 拖拽事件冲突 mousedown
  private eventConflictTime: number = 0

  /**
   * vue 生命周期 beforeMount
   */
  public beforeMount() {
    this.updatePipeline()
  }

  /**
   * vue 生命周期 mounted
   */
  public mounted() {
    this.graphElement = this.$refs.graph as HTMLDivElement
    this.graphElement.addEventListener('click', this.cancelSelectNode)
    // DataViewStore.getUserRole({ projectId: this.projectId })
    // 附加 框选
    this.initVariable()
    // 监听浏览器窗口变化
    window.addEventListener('resize', this.initVariable)
    document.addEventListener('keyup', this.globalKeyUp)
    DataViewStore.setActionArea(EditActionArea.PIPELINE)
  }

  /**
   * 快捷键监听  ArrowDown: 下, ArrowUp: 上,  Enter: enter
   * @param $event
   */
  public globalKeyUp($event: KeyboardEvent) {
    if (this.actionArea === 'pipeline') {
      // 列表弹框出现
      if ($event.key === 'Enter' && this.deleteConfirmVisible) {
        this.confirmDeleteNode()
      }
    }
  }

  /**
   * vue 生命周期 beforeDestroy
   */
  public beforeDestroy() {
    // 清空 store 节点
    DataViewStore.initPipelineNodes()
    // selectNode 置空
    DataViewStore.changeSelectNode(null)
    // 日志置空
    DataViewStore.setRunLogs([])
    DataViewStore.setMultiSelectNodeArray([])
    // 关闭历史版本面板
    DataViewStore.setHistoryStatus(false)
    window.removeEventListener('resize', this.initVariable)
    document.removeEventListener('keyup', this.globalKeyUp)
    this.graphElement.removeEventListener('click', this.cancelSelectNode)
  }

  //  TODO =======================  节点折叠  start =======================
  // 折叠节点 Id
  private toggleNodeId: number = 0

  private toggleAttachedNode($event: MouseEvent, nodeId: number) {
    $event.stopPropagation()
    this.toggleNodeId = this.toggleNodeId === nodeId ? 0 : nodeId
  }

  // ======================= pipeline 画布 公共方法  start =======================
  /**
   * 全局 更新 画布
   */
  public updatePipeline() {
    this.getAllNodes()
  }

  /**
   * 根据 projectId 和 pipelineId 获取当前project所有节点
   */
  public getAllNodes() {
    if (this.pipelineId != null) {
      const parameters = {
        id: this.projectId,
        pipelineId: this.pipelineId,
      }
      DataViewStore.queryPipelineNodes(parameters)
    }
  }

  /**
   * 初始化画布 位置 尺寸  containerPos  containerHeightWidth
   * 获取容器初始化尺寸， 设置画布初始尺寸
   */
  public initVariable() {
    try {
      // 获取 容器 位置和宽高
      const containerRect = (this.$refs
        .graphContent as HTMLDivElement).getBoundingClientRect()
      this.containerPos.x = containerRect.x
      this.containerPos.y = containerRect.y
      this.containerHeightWidth.width = containerRect.width
      this.containerHeightWidth.height = containerRect.height
      this.initGraphSizeByNodes()
    } catch (error) {
      console.log(error)
    }
  }

  /**
   * 根据 列 位置计算 节点 x 位置 -节点 css 等
   * @param col 列
   */
  private calcXPositionByCol(col: number) {
    return (col - 1) * this.gridSizeWidth + this.differenceOfGridWidth
  }

  /**
   * 根据 行 位置计算 节点 y 位置 -节点 css 等
   * @param row 行
   */
  private calcYPositionByRow(row: number) {
    // TODO 附属节点 data?: any
    // if(data && data.parametersTable) {
    //   if (this.toggleNodeId === data.parametersTable.rootNode) {
    //     return (row - 1) * this.gridSizeHeight + this.differenceOfGridHeight + data.parametersTable.index * (4 + this.nodeHeight)
    //   }
    //   return (row - 1) * this.gridSizeHeight + this.differenceOfGridHeight + data.parametersTable.index * 4
    // }
    return (row - 1) * this.gridSizeHeight + this.differenceOfGridHeight
  }

  /**
   * 根据所有节点的中最大 col，row 最大位置计算 画布尺寸
   */
  public initGraphSizeByNodes() {
    // 所有节点最大行和列
    const maxRow: number = Math.max(
      ...this.operatorNodes?.map((item) => {
        return item.data.position.row
      })
    )
    const maxCol: number = Math.max(
      ...this.operatorNodes?.map((item) => {
        return item.data.position.col
      })
    )
    // 根据最大行列 计算最大位置
    const xMaxPosition: number =
      this.calcXPositionByCol(maxCol) +
      this.nodeWidth +
      this.differenceOfGridWidth
    const yMaxPosition: number =
      this.calcYPositionByRow(maxRow) +
      this.nodeHeight +
      this.differenceOfGridHeight
    // 计算 画布尺寸， 最小为容器尺寸
    this.graphSize.width = Math.max(
      xMaxPosition,
      this.containerHeightWidth.width
    )
    this.graphSize.height = Math.max(
      yMaxPosition,
      this.containerHeightWidth.height
    )
  }

  /**
   * 取消选择的节点 , 等一些 字段初始化
   */
  public cancelSelectNode() {
    this.clickedAdd = false
    // 有 menu 的情况不清空 selectNode
    if (
      this.additionalNodeMenuStatus ||
      this.nodeMenuStatus ||
      this.lineAddNodeMenuStatus ||
      this.edgeMenuInfo.show ||
      this.nodeEditNameStatus
    ) {
      this.additionalNodeMenuStatus = false // 节点后追加节点 menu
      this.nodeMenuStatus = false // 节点编辑 复制 禁用 删除 menu
      this.lineAddNodeMenuStatus = false // 连接线插入节点 menu
      this.edgeMenuInfo.show = false // 连接线编辑 删除 menu
      this.nodeEditNameStatus = false // 节点name处在编辑状态
    } else {
      const nowTime = new Date().getTime() - this.brushStartTime
      if (nowTime < 300) {
        // 取消选择 节点
        // this.selectedNode = null
        DataViewStore.changeSelectNode(null)
        this.chooseBoxNodes = []
        DataViewStore.clearMultiSelectNodeArray()
        this.draggingNodeItem = []
        DataViewStore.clearLocateNodeArray()
        DataViewStore.setVRPanelShow(false)
        // 清空高亮线
        this.connectionLineHighlight = []
        this.closeLog()
        this.closeFavorite()
        this.closeHistory()
        this.closeShot()
      }
    }
    // 取消 追加节点(点击右边追加节点按钮的节点, 按钮显示问题)
    this.editCurrentNode = null
  }

  /**
   * 关闭所有打开面板及效果
   */
  public closeAll() {
    DataViewStore.changeSelectNode(null)
    this.chooseBoxNodes = []
    DataViewStore.clearMultiSelectNodeArray()
    this.draggingNodeItem = []
    DataViewStore.clearLocateNodeArray()
    DataViewStore.setVRPanelShow(false)
    this.clickedAdd = false
    // 清空高亮线
    this.connectionLineHighlight = []
    this.closeLog()
    this.closeFavorite()
    this.closeHistory()
    this.closeShot()
  }

  /**
   * 选择节点  胶囊 NodeCapsule.vue $emit
   * ($emit)
   * @param eventInfo
   */
  public selectNode(eventInfo: any) {
    // todo 已经选择得节点 取消选择吗还有 selectNode 问题
    const { node, e } = eventInfo
    console.log(node)
    if (node.type === 6) {
      return
    }
    console.log(node)
    if (this.isCtrlSelect(e)) {
      DataViewStore.addMultiSelectNodeArray(node)
    } else {
      DataViewStore.setShowColumnIndex(false)
      DataViewStore.clearMultiSelectNodeArray()
      if (this.selectedNode && this.selectedNode.id === node.id) {
        // DataViewStore.changeSelectNode(null)
        DataViewStore.clearMultiSelectNodeArray()
        DataViewStore.setVRPanelShow(false)
      } else {
        DataViewStore.changeSelectNode(node)
        DataViewStore.clearTableSelection()
        // DataViewStore.setVRPanelShow(true)
        DataViewStore.setTableData({
          // 切换节点时清空数据
          head: [],
          data: [],
        })
      }
      // 取消框选
      this.chooseBoxNodes = []
    }
  }

  /**
   * 选择节点  胶囊 NodeCapsuleCircle.vue $emit
   * ($emit)
   * @param eventInfo
   */
  public selectCircleNode(eventInfo: any) {
    const { node } = eventInfo
    if (node.data.input.length === 0 || !node.parentId) {
      this.$message.warning('请为图构建节点添加关联节点 !')
      return
    }
    const tableList: { label: any; tableName: any }[] = []
    node.data.input.forEach((item: any) => {
      tableList.push({
        label: item.nodeName,
        tableName: item.tableName,
      })
    })
    GraphViewStore.setGraphSourceTables(tableList)
    this.$router.push(this.$route.path.replace(/data/, `graph/${node.id}`))
  }

  // ======================= pipeline 执行 start =======================
  // 轮询
  public myInterval: any
  // 运行状态  节点status map
  public runStatusMap: any | null = null
  // 多个sessionId
  private sessionIds: number[] = []
  // 起跑节点
  private runStartNode: any | null = null

  // 确认编辑节点参数 时间
  private get confirmEditTime() {
    return DataViewStore.confirmEditTime
  }

  // 监听 重新执行
  @Watch('confirmEditTime')
  public changeEditTime() {
    this.runPipeline(true)
  }

  /**
   * pipeline 节点关系改变 执行
   */
  private pipelineNodeRelationshipsChange(node: any) {
    this.runStartNode = node
    DataViewStore.setConfirmEditTime(new Date().getTime())
  }

  /**
   * pipeline 执行 获取当此 sessionId
   */
  public runPipeline(type: boolean) {
    // console.log('runPipeline',type)
    DataViewStore.setExecutionCompletionType(false)
    // taskId 为当前编辑的节点
    // let taskId: number = 0
    let taskId: string = '0'
    let taskNode: any = {}
    if (type) {
      //   此处增加了editNode，考虑到自定义算子-确认 之后 立刻执行，可能节点是没有选中的状态
      // taskId = this.runStartNode
      //   ? this.runStartNode.id
      //   : this.selectedNode
      //   ? this.selectedNode.id
      //   : this.editNode.id // 有 startNode 说明是编辑节点关系后的执行

      taskNode = this.runStartNode
        ? this.runStartNode
        : this.selectedNode
        ? this.selectedNode
        : this.editNode
      taskId = taskNode.id
      // console.log('kkk',this.runStartNode,this.selectedNode,this.editNode)
      // console.log(this.runStartNode && !this.selectedNode && !this.editNode)
      if (
        this.runStartNode &&
        !this.selectedNode &&
        !this.editNode &&
        (taskNode.type === 8 || taskNode.type === 1)
      ) {
        return
      } // 该类型节点需配置后执行，不自动执行
    }
    if (DataViewStore.multiSelectNodeArray.length > 0) {
      this.$message.info(
        `开始部分执行节点：${DataViewStore.multiSelectNodeArray
          .map((item: any) => {
            return item.name
          })
          .toString()}`
      )
      taskId = DataViewStore.multiSelectNodeArray
        .map((item: any) => {
          return item.id
        })
        .toString()
    } else if (taskId !== '0') {
      this.$message.info(`开始执行节点：${taskNode?.name}`)
    } else {
      this.$message.info('开始全部执行')
    }
    const parameters = {
      id: this.pipelineId,
      taskId,
      projectId: this.projectId,
    }
    pipelineExecute({ data: parameters })?.then((response: any) => {
      if (response.data.code === 100) {
        DataViewStore.setExecutionStart(true) // 设置开始执行状态
        const sessionIdList = response.data.result
        if (typeof sessionIdList === 'number') {
          this.sessionIds.push(sessionIdList)
        } else {
          this.sessionIds.push(sessionIdList[0])
          if (sessionIdList[1]) {
            this.fullDataSessionIds.push(sessionIdList[1])
          }
          DataViewStore.setFullAmountTask(true)
        }
        this.runStatusPoll()
        this.runStartNode = null
      }
    })
  }

  /**
   * pipeline 停止 传当此 sessionId
   */
  public async stopPipeline() {
    const array = this.sessionIds.concat(this.fullDataSessionIds)
    const parameters = {
      projectId: this.projectId,
      sessionId: array.toString(),
    }
    const response = await pipelineStop({ data: parameters })
    if (response.data.code === 100) {
      // this.replaceExistingStatus([])
      clearInterval(this.myInterval)
      clearInterval(this.myFullInterval)
      this.getRunStatus(this.sessionIds)
      this.fullDataSessionIds = []
      this.sessionIds = []
      // await DataViewStore.queryPipelineNodes({
      //   id: this.projectId,
      //   pipelineId: this.pipelineId,
      // })
      this.$message.success(response.data.result)
      DataViewStore.setFullAmountTask(true)
      DataViewStore.setExecutionStart(false)
    } else {
      clearInterval(this.myFullInterval)
      this.$message.error(response.data.result)
    }
  }

  /**
   * 轮询拿 pipeline 节点运行状态
   */
  public runStatusPoll() {
    clearInterval(this.myInterval)
    clearInterval(this.myFullInterval)
    // 先请求一次状态
    this.getRunStatus(this.sessionIds)
    this.myInterval = setInterval(() => {
      if (this.sessionIds.length > 0) {
        this.getRunStatus(this.sessionIds)
      }
    }, 1000)
  }

  // 全量数据的轮询
  public myFullInterval: any
  // 全量数据执行 sessionIds
  public fullDataSessionIds: number[] = []

  /**
   * 全量数据 轮询
   */
  public fullRunStatusPoll() {
    clearInterval(this.myFullInterval)
    if (this.fullDataSessionIds.length > 0) {
      // 先请求一次状态
      this.getFullRunStatus(this.fullDataSessionIds)
      this.myFullInterval = setInterval(() => {
        this.getFullRunStatus(this.fullDataSessionIds)
      }, 1000)
    }
  }

  /**
   * 全量数据的轮询
   */
  public async getFullRunStatus(sessionId: number[]) {
    const parameters = {
      pipelineId: this.pipelineId,
      sessionId: sessionId.toString(),
    }
    // console.log('getFullRunStatus',parameters.sessionId)
    const response: any = await pipelineInstanceStatus({ data: parameters })
    if (response.data.code === 100) {
      // 处理多个 sessionId 请求的状态
      // const taskMap: any = {}
      const statusList: string[] = []
      response.data.result.sessionLogs.forEach((item: any) => {
        // merge(taskMap, item.taskMap)
        statusList.push(item.status)
      })
      // 全量数据跑完，销毁轮询
      // if(taskMap[`${this.selectedNode.id}`].status === 'SUCCESS') {  // selectNode 执行完
      if (!statusList.includes('RUNNING')) {
        clearInterval(this.myFullInterval)
        await DataViewStore.queryPipelineNodes({
          id: this.projectId,
          pipelineId: this.pipelineId,
        })
        this.fullDataSessionIds = []
        DataViewStore.setExecutionCompletionTime(new Date().getTime())
        DataViewStore.setExecutionCompletionType(true)
        DataViewStore.setExecutionStart(false)
        // this.$message.info('执行完毕')
        DataViewStore.setFullAmountTask(false)
      }
    }
  }

  /**
   * pipeline 节点运行状态 轮询接口
   * @param sessionId 执行的sessionId
   */
  public async getRunStatus(sessionId: number[]) {
    const parameters = {
      pipelineId: this.pipelineId,
      sessionId: sessionId.toString(),
    }
    // console.log('getRunStatus')
    const response: any = await pipelineInstanceStatus({ data: parameters })
    if (response.data.code === 100) {
      // 处理多个 sessionId 请求的状态
      const taskMap: any = {}
      const taskLogs: any[] = []
      const statusList: string[] = []
      response.data.result.sessionLogs.forEach((item: any) => {
        merge(taskMap, item.taskMap)
        taskLogs.push(item.totalLogs)
        statusList.push(item.status)
      })

      // 合并节点状态
      this.replaceExistingStatus(taskMap)
      // 销毁轮询
      if (!statusList.includes('RUNNING')) {
        clearInterval(this.myInterval)
        await DataViewStore.queryPipelineNodes({
          id: this.projectId,
          pipelineId: this.pipelineId,
        })
        this.sessionIds = []
        DataViewStore.setExecutionCompletionTime(new Date().getTime())
        DataViewStore.setExecutionCompletionType(false)
        // simple 执行完成 执行全量的
        this.fullRunStatusPoll()
      }
      // 日志
      DataViewStore.setRunLogs(taskLogs)
    }
  }

  /**
   * 运行过程及结束， 节点的状态（新状态替换旧状态， 没有运行的节点不变）
   */
  private replaceExistingStatus(taskMap: any) {
    const newTaskMap = Object.keys(taskMap)
    this.runStatusMap = this.runStatusMap ? this.runStatusMap : {}
    newTaskMap.forEach((id: string) => {
      this.$set(this.runStatusMap, id, {
        status: taskMap[id].status,
        logInfo: taskMap[id].logInfo,
      })
    })
    // console.log('runStatusMap',this.runStatusMap)
    DataViewStore.setNodeStatusMap(this.runStatusMap)
  }

  /**
   * 节点运行状态 运行动画（执行）
   * @param nodeId  节点ID
   */
  public getNodeStatus(nodeId: number) {
    let status: string = ''
    if (
      this.runStatusMap != null &&
      Object.keys(this.runStatusMap).includes(`${nodeId}`)
    ) {
      status = this.runStatusMap[`${nodeId}`].status
    }
    return status
  }

  /**
   * 节点运行状态 和 tip: string 传状态给节点胶囊
   * @param nodeId  节点ID
   */
  public getNodeStatusTip(nodeId: number) {
    const nodeRunStatus = {
      status: '',
      tip: '',
    }
    if (
      this.runStatusMap != null &&
      Object.keys(this.runStatusMap).includes(`${nodeId}`)
    ) {
      nodeRunStatus.status = this.runStatusMap[`${nodeId}`].status
      const { logInfo } = this.runStatusMap[`${nodeId}`]
      // console.log(logInfo)
      if (logInfo?.includes('error_msg')) {
        // 不然json转换会报错
        nodeRunStatus.tip = logInfo
          ? `${JSON.parse(logInfo.replace(/[\s+]/g, ' ')).error_msg.slice(
              0,
              300
            )}...`
          : ''
      }
    }
    return nodeRunStatus
  }

  // =======================  关于节点的操作 start =======================
  // start 节点后添加节点， 连线添加节点 复制节点 （禁用，删除 在nodeCapsule组件中， 通知更新pipeline）
  public editCurrentNode: any = null // 当前的节点 要追加子节点的节点
  private additionalNodeMenuStatus: boolean = false // 节点后追加节点的menu 状态
  private nodeMenuStatus: boolean = false // 所有节点的 menu(复制，禁用，删除) 是否也有展开
  private nodeEditNameStatus: boolean = false // 节点编辑 name 状态
  /**
   * 点击 右边添加按钮添加后面节点,
   * @param node
   * @param $event
   */
  public editAdditionalNode(node: Node, $event: MouseEvent) {
    // add菜单和节点右键菜单不可同时展开
    this.menuOpenNodeId = null // dropdown menu 关闭
    this.clickedAdd = true // 记录点击
    $event.stopPropagation()
    this.editCurrentNode = node
    this.additionalNodeMenuStatus = true
  }

  /**
   * 得到 添加节点menu的状态， addmenu 组件通知
   * @param addInfo {nodeId: 节点 id, nodePosition: 节点位置, operator： 添加的算子}
   */
  public nodeAddNode(addInfo: any) {
    // 手动关闭dropdown menu
    this.clickedAdd = false
    // 在下一列 找一个空位
    const position = ComputeSpaceAfterNode(this.operatorNodes, addInfo)
    if (
      Object.values(this.nodeMap)
        .flat(200)
        .includes(`${position.addRow}-${position.addCol}`)
    ) {
      const array = Object.values(this.nodeMap).flat(200).reverse()
      const kk: any = array.find((item: any) => {
        return item.split('-')[0] === position.addRow.toString()
      })
      position.addCol = toInteger(kk.split('-')[1]) + 1
    }

    const parameters = {
      name: addInfo.operator.name,
      projectId: this.projectId,
      pipelineId: this.pipelineId,
      parentId: addInfo.nodeId,
      childId: null,
      type: addInfo.operator.type, // 类型: 3--表示数据节点， 1--算子， 2--ETL算子, 7---model
      data: {
        modelId: addInfo.operator.id,
        algName: addInfo.operator.name,
        algType: addInfo.operator.algType,
        position: {
          row: position.addRow,
          col: position.addCol,
        },
      },
    }
    taskComplexLoad({ data: parameters })?.then((response: any) => {
      if (response.data.code === 100) {
        this.$message.success('追加节点成功 !', 1)
        this.updatePipeline()
        this.editCurrentNode = null
        this.$nextTick(() => {
          this.pipelineNodeRelationshipsChange(response.data.result)
        })
      } else {
        this.$message.error(response.data.message)
      }
    })
  }

  // ========================= 连接线 添加节点 操作 start =======================
  // 连接线添加节点
  private currentLine: any = null
  // dropdown 状态 暂时没有使用
  private btnStatus: boolean = false
  // line menu 状态
  private lineAddNodeMenuStatus: boolean = false
  // 位置
  private lineAddMenuPosition: Position = { x: -100, y: -100 }
  // 连线编辑 ,menu
  public edgeMenuInfo: any = {
    show: false,
    x: -1000,
    y: -1000,
    targetInfo: { startId: 0, endId: 0 },
  }

  /**
   * 删除连接线 menu 及位置
   */
  public EmitEditLine(currentEdgeInfo: any) {
    this.edgeMenuInfo.show = true
    this.edgeMenuInfo.x = currentEdgeInfo.positionX + 4
    this.edgeMenuInfo.y = currentEdgeInfo.positionY + 2
    this.edgeMenuInfo.targetInfo.startId = currentEdgeInfo.edge.startNodeId
    this.edgeMenuInfo.targetInfo.endId = currentEdgeInfo.edge.endNodeId
  }

  /**
   * 删除连接线
   */
  public async deleteConnectLine() {
    const parameters = {
      pipelineId: this.pipelineId,
      parentId: this.edgeMenuInfo.targetInfo.startId,
      childId: this.edgeMenuInfo.targetInfo.endId,
      projectId: this.projectId,
    }
    deleteLineById({ data: parameters })?.then((response: any) => {
      if (response.data.code === 100) {
        this.$message.success('删除节点关系成功 !', 1)
        this.edgeMenuInfo.show = false
        this.updatePipeline()
      }
    })
  }

  /**
   * 连接线信息
   * （$emit）
   * @param menuInfo 当前连接线
   */
  public clickAddNode(menuInfo: any) {
    const { position, edge } = menuInfo
    this.lineAddMenuPosition.x = edge.x + position.x
    this.lineAddMenuPosition.y = edge.y + position.y
    this.btnStatus = true
    this.lineAddNodeMenuStatus = false
    this.currentLine = edge
    // 切换位置
    this.$nextTick(() => {
      setTimeout(() => {
        this.lineAddNodeMenuStatus = true
      }, 30)
    })
  }

  /**
   * 连接线添加节点
   * ($emit)
   * operator  要添加加的算子
   */
  public relationLineAddNode(operator: any) {
    this.lineAddNodeMenuStatus = false
    const { currentLine } = this
    const parameters = {
      name: operator.name,
      projectId: this.projectId,
      pipelineId: this.pipelineId,
      parentId: currentLine.startNodeId,
      childId: currentLine.endNodeId,
      type: operator.type, // 类型: 3--表示数据节点， 1--算子， 2--ETL算子
      data: {
        modelId: operator.id,
        algName: operator.name,
        algType: operator.algType,
        position: {
          row: 0,
          col: 0,
        },
      },
    }
    taskComplexLoad({ data: parameters })?.then((response: any) => {
      if (response.data.code === 100) {
        this.$message.success('插入节点成功 !', 1)
        this.updatePipeline()
        this.editCurrentNode = null
        this.$nextTick(() => {
          this.pipelineNodeRelationshipsChange(response.data.result)
        })
      } else if (response.data.code === 630) {
        this.updatePipeline()
      } else {
        this.$message.error(response.data.message)
      }
    })
  }

  // =======================  拖拽相关  start  =======================

  // 移动初始位置  startDragging 开始拖拽的时候计算
  private gridStartDragPosition: PositionWithColRow = { col: -1, row: -1 }
  // 鼠标所在行列 拖拽过程中
  private cursorPositionWithColRow: PositionWithColRow = { col: -1, row: -1 }
  // 鼠标其实位置
  private mouseStartPositionX: number = 0
  private mouseStartPositionY: number = 0

  @Watch('cursorPositionWithColRow', { deep: true })
  private cursorPositionChange(value: PositionWithColRow) {
    // ------ 鼠标移动过程中 网格示意位置， 根据网格尺寸取整------
    this.nodeGridPosition = value
    // ------关系编辑的位置 鼠标实际位置 只有当拖拽一个节点才会设置值， 并进行计算
    if (this.draggingNodeItem.length === 1) {
      this.nodeRelevancePosition = value
    }
  }

  /**
   * 开始拖拽 节点拖拽改变位置
   * @param $event
   * @param node  当前节点信息
   */
  public startDragging($event: MouseEvent, node: Node) {
    this.pipelineGraphSize()
    $event.stopPropagation()
    this.eventConflictTime = new Date().getTime()
    this.mouseStartPositionX = $event.x
    this.mouseStartPositionY = $event.y
    // 画布容器
    this.graphContent = this.$refs.graphContent as HTMLElement
    // 准备工作
    this.startDragPreparatoryWork(node)
    // 根据行列 位置 计算所有要拖拽虚拟节点 相对 xy 位置  应该是计算问题
    this.draggingNodeItem.forEach((item: any, index: number) => {
      this.$set(
        this.draggingNodeItem[index].data.position,
        'x',
        this.calcXPositionByCol(this.draggingNodeItem[index].data.position.col)
      )
      this.$set(
        this.draggingNodeItem[index].data.position,
        'y',
        this.calcYPositionByRow(this.draggingNodeItem[index].data.position.row)
      )
    })
    // 计算网格 拖拽 初始 位置
    this.gridStartDragPosition.col = Math.min(
      ...this.draggingNodeItem.map((item) => {
        return item.data.position.col
      })
    )
    this.gridStartDragPosition.row = Math.min(
      ...this.draggingNodeItem.map((item) => {
        return item.data.position.row
      })
    )

    // 拖拽 添加鼠标移动 抬起监听事件
    document.addEventListener('mousemove', this.dragMouseMoveListener)
    document.addEventListener('mouseup', this.dragMouseUpListener)
  }

  /**
   * 拖拽开始准备工作
   */
  public startDragPreparatoryWork(node: any) {
    // this.panelDragOperator = node.type === 6?? false  // pipeline 内部拖拽 除图构建节点全部允许 jion union
    // 没有框选  拖拽只关注 draggingNodeItem
    // TODO 当框选选择的单个节点 与当前点击（拖拽）的不是同一个节点， 清空选择拖拽的数组
    if (
      this.draggingNodeItem.length === 0 ||
      (this.draggingNodeItem.length === 1 &&
        this.draggingNodeItem[0].id !== node.id)
    ) {
      this.draggingNodeItem = []
      // 单个节点拖拽
      this.draggingNodeItem.push(cloneDeep(node))
    } else if (this.draggingNodeItem.length >= 1) {
      const chooseNodeIds = this.draggingNodeItem.map((item) => {
        return item.id
      })
      if (!chooseNodeIds.includes(node.id)) {
        // 点击的节点在框选之外
        this.draggingNodeItem = []
        // 单个节点拖拽
        this.draggingNodeItem.push(cloneDeep(node))
      } else if (chooseNodeIds[0] !== node.id) {
        // 包含点击的节点，但不在第一位，把node提到第一位
        const index = chooseNodeIds.indexOf(node.id)
        this.draggingNodeItem.splice(index, 1, this.draggingNodeItem[0])
        this.draggingNodeItem[0] = cloneDeep(node)
      }
    }
    // 节点拖拽的col范围
    const { colParentMax, colChildMin } = getParentSonPosition(
      this.operatorNodes,
      this.draggingNodeItem
    )
    this.colParentMax = colParentMax
    this.colChildMin = colChildMin

    // 框选节点中左上角节点的位置

    // 计算网格尺寸
    const { rowCount, colCount } = calcNodeGridSize(this.draggingNodeItem)
    this.nodeGridSize.rowCount = rowCount
    this.nodeGridSize.colCount = colCount
  }

  /**
   * 拖拽结束 节点拖拽鼠标抬起监听  update node position
   */
  public async dragMouseUpListener() {
    // 移除 鼠标移动 抬起监听事件
    document.removeEventListener('mousemove', this.dragMouseMoveListener)
    document.removeEventListener('mouseup', this.dragMouseUpListener)
    this.mouseStartPositionX = 0
    this.mouseStartPositionY = 0
    if (this.dragging) {
      // 关系编辑关闭
      this.nodeRelevanceShow = false
      // 拖拽 标志
      this.dragging = false
      // 移动条件成立  通过监听 nodeGridPosition 改变其值
      if (
        this.gridDraggingShow &&
        this.nodeGridPosition.col > 0 &&
        this.nodeGridPosition.row > 0
      ) {
        // 框选  chooseBoxNodes 作为框选标示
        // 移动网格位置 和 初始 行列 差值
        const colNumberDiff: number =
          this.nodeGridPosition.col - this.gridStartDragPosition.col
        const rowNumberDiff: number =
          this.nodeGridPosition.row - this.gridStartDragPosition.row
        // 修改节点位置  包括单个或者多个 原位置不更新
        if (!(colNumberDiff === 0 && rowNumberDiff === 0)) {
          this.draggingNodeItem.forEach((item1: any) => {
            const { id } = item1
            const node = this.operatorNodes.find((item) => item.id === id)
            if (node) {
              node.data.position.col += colNumberDiff
              node.data.position.row += rowNumberDiff

              const newNodePosition: PositionWithColRow = {
                col: node.data.position.col,
                row: node.data.position.row,
              }

              this.changePosition(node, newNodePosition)?.then(
                (response: any) => {
                  if (response.data.code !== 100) {
                    // this.$message.error(`${node.name} 位置修改失败 !`, 1)
                    this.$message.error(response.data.message, 1)
                    node.data.position.col -= colNumberDiff
                    node.data.position.row -= rowNumberDiff
                  }
                  this.$nextTick(() => {
                    this.initGraphSizeByNodes()
                  })
                }
              )
            }
          })
          // 更新选中node的位置
          this.draggingNodeItem.forEach((node: any) => {
            node.data.position.col += colNumberDiff
            node.data.position.row += rowNumberDiff
          })
        }

        // 如果没有框选就清空  todo
        if (this.chooseBoxNodes.length === 0) {
          this.draggingNodeItem = []
        }
        // 网格位置初始化
        this.gridDraggingShow = false
      }
    }
  }

  /**
   * 节点拖拽鼠移动监听
   * @param $event
   */
  public dragMouseMoveListener($event: MouseEvent) {
    // 拖拽 和 点击 区分
    const offsetX = Math.abs($event.x - this.mouseStartPositionX)
    const offsetY = Math.abs($event.y - this.mouseStartPositionY)
    // if(!this.dragging && (new Date().getTime() - this.eventConflictTime > 50)) {
    if (
      !this.dragging &&
      (offsetX > 6 || offsetY > 6) &&
      !this.isVisitor &&
      !this.isLock
    ) {
      this.dragging = true
    }
    const { graphContent } = this

    // 修改所有虚拟节点位置
    const xPositionChange =
      $event.x -
      this.containerPos.x +
      graphContent.scrollLeft -
      (1 / 2) * this.nodeWidth -
      this.draggingNodeItem[0].data.position.x
    const yPositionChange =
      $event.y -
      this.containerPos.y +
      graphContent.scrollTop -
      (1 / 2) * this.nodeHeight -
      this.draggingNodeItem[0].data.position.y

    this.draggingNodeItem.forEach((item: any, index: any) => {
      // 虚拟节点  实时跟随鼠标
      this.draggingNodeItem[index].data.position.x += xPositionChange
      this.draggingNodeItem[index].data.position.y += yPositionChange
    })
    // 单节点拖拽
    // this.draggingNodeItem[0].data.position.x = $event.x - this.containerPos.x + graphContent.scrollLeft - (1/2 * this.nodeWidth)
    // this.draggingNodeItem[0].data.position.y = $event.y - this.containerPos.y + graphContent.scrollTop - (1/2 * this.nodeHeight)

    // 鼠标拖拽移动 行列位置
    if (this.draggingNodeItem.length <= 1) {
      this.cursorPositionWithColRow.col =
        Math.floor(
          ($event.x - this.containerPos.x + graphContent.scrollLeft) /
            this.gridSizeWidth
        ) + 1
      this.cursorPositionWithColRow.row =
        Math.floor(
          ($event.y - this.containerPos.y + graphContent.scrollTop) /
            this.gridSizeHeight
        ) + 1
    } else {
      const targetX =
        ($event.x - this.containerPos.x + graphContent.scrollLeft) /
        this.gridSizeWidth
      const targetY =
        ($event.y - this.containerPos.y + graphContent.scrollTop) /
        this.gridSizeHeight
      // 在按照选中节点移动位置确定目标框左上角位置后，还要加上选中节点在框选所有节点中的相对位置
      this.cursorPositionWithColRow.col =
        Math.floor(targetX) +
        1 -
        (this.draggingNodeItem[0].data.position.col -
          this.gridStartDragPosition.col)
      this.cursorPositionWithColRow.row =
        Math.floor(targetY) +
        1 -
        (this.draggingNodeItem[0].data.position.row -
          this.gridStartDragPosition.row)
    }
  }

  /**
   * 画布尺寸扩容Math.floor(($event.x - this.containerPos.x + graphContent.scrollLeft) / this.gridSizeWidth) + 1
   */
  public pipelineGraphSize() {
    let timer: number = 0
    const that = this as any
    const document = (window as any).document as any
    const mousemove = function ($moveEvent: MouseEvent) {
      const screeWidth = that.graphContent.clientWidth + that.containerPos.x
      // 画布容器下边框相对浏览器位置 y
      const screeHeight = that.graphContent.clientHeight + that.containerPos.y
      if ($moveEvent.pageX - screeWidth > -20 && Date.now() - timer > 400) {
        that.graphSize.width += that.gridSizeWidth
        that.graphContent.scrollLeft += that.gridSizeWidth
        timer = Date.now()
      }
      if ($moveEvent.pageY - screeHeight > -20 && Date.now() - timer > 400) {
        that.graphSize.height += that.gridSizeHeight
        that.graphContent.scrollTop += that.gridSizeHeight
        timer = Date.now()
      }
      if (
        $moveEvent.pageX - that.containerPos.x < 20 &&
        Date.now() - timer > 400
      ) {
        that.graphContent.scrollLeft -= that.gridSizeWidth
        timer = Date.now()
      }
      if (
        $moveEvent.pageY - that.containerPos.y < 20 &&
        Date.now() - timer > 400
      ) {
        that.graphContent.scrollTop -= that.gridSizeHeight
        timer = Date.now()
      }
    }
    document.addEventListener('mousemove', mousemove)
    const mouseup = function () {
      document.removeEventListener('mousemove', mousemove)
      document.removeEventListener('mouseup', mouseup)
    }
    document.addEventListener('mouseup', mouseup)
  }

  /**
   * 修改节点位置   单个修改
   * @param node
   * @param newPosition
   */
  public changePosition(node: any, newPosition: PositionWithColRow) {
    const parameters = {
      id: node.id,
      name: node.name,
      projectId: node.projectId,
      pipelineId: node.pipelineId,
      data: {
        position: newPosition,
        isSimple: true,
      },
    }
    return simpleUpdate({ data: parameters })
  }

  // =======================  拖拽相关 end =======================

  // =======================  拖拽连接 建立关系  start =======================

  /**
   * 拖拽到添加连接  panel新节点连接 或者 pipeline内部节点创建关系
   * (mouseup)
   */
  public nodeConnection() {
    // 从 左侧 panel 拖拽
    if (this.panelDragMark) {
      // 区分数据 和 算子
      if ('algType' in this.nodeDragFromPanel) {
        const data = {
          // modelId: this.nodeDragFromPanel.id,
          algName: this.nodeDragFromPanel.name,
          algType: this.nodeDragFromPanel.algType,
          position: {
            row: 0,
            col: 0,
          },
        }
        this.dragConnectNode(
          this.connectedNode,
          this.nodeDragFromPanel,
          this.nodeDragFromPanel.type,
          data
        )
      } else {
        //  数据节点
        const data = {
          dataset_id: this.nodeDragFromPanel.id,
          position: {
            row: 0,
            col: 0,
          },
        }
        this.dragConnectNode(
          this.connectedNode,
          this.nodeDragFromPanel,
          3,
          data
        )
      }
    } else {
      const parameters = {
        projectId: this.projectId,
        pipelineId: this.pipelineId,
        parentId: this.draggingNodeItem[0].id,
        childId: this.connectedNode.id,
      }
      taskConnectLine({ data: parameters })?.then((response: any) => {
        if (response.data.code === 100) {
          this.$message.success('节点添加关联成功 !', 0.5)
          this.updatePipeline()
          this.editCurrentNode = null
          this.initGraphSizeByNodes()
          this.$nextTick(() => {
            this.pipelineNodeRelationshipsChange(this.connectedNode)
          })
        }
      })
    }
  }

  /**
   * 连接接口， 新节点建立关系 panel 拖拽
   * nodeConnection() 调用
   * @param connectedNode
   * @param nodeDragFromPanel
   * @param type
   * @param data
   */
  public dragConnectNode(
    connectedNode: any,
    nodeDragFromPanel: any,
    type: number,
    data: any
  ) {
    const parameters = {
      name: nodeDragFromPanel.name,
      projectId: this.projectId,
      pipelineId: this.pipelineId,
      parentId: null,
      childId: connectedNode.id,
      type,
      data,
    }
    taskComplexLoad({ data: parameters })?.then((response: any) => {
      if (response.data.code === 100) {
        this.$message.success('添加节点并关联关系成功 !', 1)
        this.updatePipeline()
        this.editCurrentNode = null
        this.$nextTick(() => {
          this.pipelineNodeRelationshipsChange(response.data.result)
        })
      } else if (response.data.code === 630) {
        this.updatePipeline()
      } else {
        this.$message.error(response.data.message)
      }
    })
    // 关闭 panel 拖拽标志
    this.panelDragMark = false
    // 隐藏 关系编辑
    this.nodeRelevanceShow = false
  }

  /**
   * 并集 连接  (panel / pipeline)
   */
  public nodeUnionJoin(algType: number) {
    if (this.panelDragMark) {
      const type =
        this.nodeDragFromPanel &&
        typeof this.nodeDragFromPanel.type === 'string'
          ? 3
          : this.nodeDragFromPanel.type
      const parameters = {
        projectId: this.projectId,
        params: [
          // 拖拽的节点
          {
            name: this.nodeDragFromPanel.name,
            projectId: this.projectId,
            pipelineId: this.pipelineId,
            parentId: null,
            childId: null,
            type,
            data: {
              dataset_id: this.nodeDragFromPanel.id,
              position: {
                row: 0,
                col: 0,
              },
            },
          },
          // join
          {
            name: algType === 2 ? 'JOIN' : 'UNION',
            projectId: this.projectId,
            pipelineId: this.pipelineId,
            parentId: `${this.connectedNode.id}`,
            childId: null,
            type: 2,
            data: {
              algName: algType === 2 ? 'JOIN' : 'UNION',
              algType,
              position: { col: 0, row: 0 },
            },
          },
        ],
      }
      this.postNodeJoin(parameters)
    } else {
      const parameters = {
        projectId: this.projectId,
        params: [
          {
            name: algType === 2 ? 'JOIN' : 'UNION',
            projectId: this.projectId,
            pipelineId: this.pipelineId,
            parentId: `${this.draggingNodeItem[0].id},${this.connectedNode.id}`,
            childId: null,
            type: 2,
            data: {
              algName: algType === 2 ? 'JOIN' : 'UNION',
              algType,
              position: { col: 0, row: 0 },
            },
          },
        ],
      }
      this.postNodeJoin(parameters)
    }
  }

  /**
   * jion 接口
   */
  public postNodeJoin(parameters: any) {
    taskAddUnionJoinNode({ data: parameters })?.then((response: any) => {
      if (response.data.code === 100) {
        this.$message.success('关联关系成功 !', 1)
        this.updatePipeline()
        this.editCurrentNode = null
        this.$nextTick(() => {
          this.pipelineNodeRelationshipsChange(response.data.result)
        })
      }
    })
    // 关闭 panel 拖拽标志
    this.panelDragMark = false
    // 隐藏 关系编辑
    this.nodeRelevanceShow = false
  }

  // =======================  拖拽连接 建立关系  end =======================

  // ======================= panel 拖拽节点 start =======================

  // 新节点位置
  private newNodePosition: PositionWithColRow = { col: -1, row: -1 }
  // 左侧 panel 拖拽标识 true
  private panelDragMark: boolean = false
  // 拖拽的数据 或算子 统称为 节点
  private nodeDragFromPanel: any = null
  // 左侧panel 拖 算子 不显示 join union
  private panelDragOperator: boolean = true

  /**
   * 从左侧列表中 拖拽新节点 拖拽过程中 落点， 以及拖拽关系编辑
   */
  public dragToNode(dragInfo: any) {
    const { event } = dragInfo
    this.nodeDragFromPanel = dragInfo.node
    // panel 拖拽标志
    this.panelDragMark = true
    // 拖拽虚拟节点大小
    this.nodeGridSize.rowCount = 1
    this.nodeGridSize.colCount = 1
    this.graphContent = this.$refs.graphContent as HTMLElement
    // 拖拽算子 不显示 jion union
    this.$nextTick(() => {
      if (typeof this.nodeDragFromPanel.type === 'string') {
        this.panelDragOperator = true
      } else {
        this.panelDragOperator = false
      }
    })
    // TODO 优化，（先判断条件成立 this.gridDraggingShow， this.nodeRelevanceShow）
    this.nodeGridPosition.col =
      Math.floor(
        (event.x -
          this.containerPos.x +
          this.graphContent.scrollLeft -
          (1 / 2) * this.nodeWidth) /
          this.gridSizeWidth
      ) + 1
    this.nodeGridPosition.row =
      Math.floor(
        (event.y -
          this.containerPos.y +
          this.graphContent.scrollTop -
          (1 / 2) * this.nodeHeight) /
          this.gridSizeHeight
      ) + 1

    // 节点是否有放置在连接线上
    const isAvaliable = Object.values(this.nodeMap)
      .flat(200)
      .includes(`${this.nodeGridPosition.row}-${this.nodeGridPosition.col}`)
    // 在所有节点中 查找是否覆盖  单个覆盖, 是否新节点 关系编辑显示
    const connectedNode = getNodeByPosition(
      this.operatorNodes,
      this.nodeGridPosition.col,
      this.nodeGridPosition.row
    )
    // 落点位置显示
    this.gridDraggingShow = !connectedNode && !isAvaliable
    // 图构建节点不可join union
    this.panelDragOperator = connectedNode?.type !== 6

    // 拖拽的目标节点 位置
    if (this.gridDraggingShow) {
      this.newNodePosition.col = this.nodeGridPosition.col
      this.newNodePosition.row = this.nodeGridPosition.row
    }
    // 关系编辑框显示
    this.nodeRelevanceShow = !!connectedNode && !this.isVisitor && !this.isLock
    // 覆盖到节点上计算 连接 union join 位置
    if (this.nodeRelevanceShow) {
      // 关系编辑的位置 鼠标实际位置
      this.nodeRelevancePosition.col =
        Math.floor(
          (event.x -
            this.containerPos.x +
            this.graphContent.scrollLeft -
            (1 / 2) * this.nodeWidth) /
            this.gridSizeWidth
        ) + 1
      this.nodeRelevancePosition.row =
        Math.floor(
          (event.y -
            this.containerPos.y +
            this.graphContent.scrollTop -
            (1 / 2) * this.nodeHeight) /
            this.gridSizeHeight
        ) + 1
    }
  }

  /**
   * 左侧 panel 数据拖拽 添加数据节点
   * @param dragInfo { dropped:{id, name, type}, event: $event}
   */
  public addData(dragInfo: any) {
    this.initRelevanceShow()
    // 拖到空位置放置
    if (
      this.gridDraggingShow &&
      this.newNodePosition.col > 0 &&
      this.newNodePosition.row > 0
    ) {
      // 关闭标志
      this.panelDragMark = false
      this.initNodeGrid()
      const parameters = {
        name: dragInfo.dropped.name,
        projectId: this.projectId,
        pipelineId: this.pipelineId,
        parentId: null,
        childId: null,
        type: 3, // 类型: 3--表示数据节点， 1--算子， 2--ETL算子
        data: {
          modelId: dragInfo?.operator?.id,
          dataset_id: dragInfo.dropped.id,
          position: this.newNodePosition,
        },
      }
      taskComplexLoad({ data: parameters })?.then((response: any) => {
        if (response.data.code === 100) {
          this.$message.success('添加数据节点成功 !', 1)
          this.updatePipeline()
          this.$nextTick(() => {
            this.pipelineNodeRelationshipsChange(response.data.result)
          })
          UserGuideStore.finishGuideStepModal() // 完成用户引导
        } else {
          UserGuideStore.setCurrentGuideStepInProgress(false) // 拖拽添加几点失败
        }
      })
    }
  }

  /**
   * 初始化落点网格
   */
  public initNodeGrid() {
    this.gridDraggingShow = false
    this.nodeGridSize.rowCount = 0
    this.nodeGridSize.colCount = 0
  }

  /**
   * 初始化 连接 jion union 显示
   */
  public initRelevanceShow() {
    this.nodeRelevanceShow = false
  }

  /**
   * 左侧 panel  算子拖拽 添加算子节点
   * @param dragInfo { dropped:{算子相关数据}, event: $event}
   */
  public addOperator(dragInfo: any) {
    this.initRelevanceShow()
    if (
      this.gridDraggingShow &&
      this.newNodePosition.col > 0 &&
      this.newNodePosition.row > 0
    ) {
      // 关闭标志
      this.panelDragMark = false
      this.initNodeGrid()
      const parameters = {
        name: dragInfo.dropped.name,
        projectId: this.projectId,
        pipelineId: this.pipelineId,
        type: dragInfo.dropped.type, // 类型: 3--表示数据节点， 1--算子， 2--ETL算子
        data: {
          modelId: dragInfo.dropped.id,
          algName: dragInfo.dropped.name,
          algType: dragInfo.dropped.algType,
          position: this.newNodePosition,
        },
      }

      taskComplexLoad({ data: parameters })?.then((response: any) => {
        if (response.data.code === 100) {
          this.$message.success('添加算子节点成功 !', 1)
          this.updatePipeline()
        } else {
          this.$message.error(response.data.message)
        }
      })
    }
  }

  // ======================= panel 拖拽节点 end =======================

  // =======================  框选节点 相关 start =======================

  // 框选 动作 框 ing
  private chooseBoxShow: boolean = false
  // 框选 多个节点， 制作标志使用
  public chooseBoxNodes: any = []
  // 刷选开始时间
  public brushStartTime: number = 0
  // 框选 动作 首尾 x y
  private chooseBoxStartEndPosition: any = {
    start: { x: -3000, y: -3000 },
    end: { x: -3000, y: -3000 },
  }

  // 框选定位 x
  private get positionX() {
    const { start, end } = this.chooseBoxStartEndPosition
    return Math.min(start.x, end.x)
  }

  // 框选定位 y
  private get positionY() {
    const { start, end } = this.chooseBoxStartEndPosition
    return Math.min(start.y, end.y)
  }

  // 框选 动作 首尾行列 col row（有 @Watch 框选结束 不能初始化 会影响框选的节点）
  private chooseBoxStartEnd: any = {
    start: { col: -1, row: -1 },
    end: { col: -1, row: -1 },
  }

  // 监听框选区域 x y
  @Watch('chooseBoxStartEndPosition', { deep: true })
  public chooseBoxStartEndChange() {
    this.getNodesByChooseBox(this.chooseBoxStartEndPosition)
  }

  /**
   * 根据框选区域 获取 节点数组
   * @param area 框选行列
   */
  public getNodesByChooseBox(area: any) {
    const l = this.positionX
    const t = this.positionY
    const w = Math.abs(area.start.x - area.end.x)
    const h = Math.abs(area.start.y - area.end.y)
    if (area.end.x !== -3000 && area.end.y !== -3000) {
      // todo 这里需要 判断是否已经清空 框选，
      const chooseBoxNodes = this.operatorNodes.filter((item) => {
        const sl =
          this.calcXPositionByCol(item.data.position.col) + this.nodeWidth
        const st =
          this.calcYPositionByRow(item.data.position.row) + this.nodeHeight
        return (
          // item.data.position.row > area.start.row &&
          // item.data.position.row <= area.end.row &&
          // item.data.position.col > area.start.col &&
          // item.data.position.col <= area.end.col
          sl > l &&
          st > t &&
          this.calcXPositionByCol(item.data.position.col) < l + w &&
          this.calcYPositionByRow(item.data.position.row) < t + h
        )
      })
      if (
        chooseBoxNodes.find((item) => {
          return item.id === this.selectedNode?.id
        })
      ) {
        DataViewStore.changeSelectNode(null)
      }
      // 记录框选 节点
      this.chooseBoxNodes = chooseBoxNodes
      DataViewStore.setMultiSelectNodeArray(chooseBoxNodes)
      // 框选后的节点 就是 要拖拽的节点
      this.draggingNodeItem = []
      chooseBoxNodes.forEach((node: any) => {
        this.draggingNodeItem.push(cloneDeep(node))
      })
    }
  }

  // 框选开关
  private chooseSwitch: boolean = true

  /**
   * 开始 框选 （框选动作框）
   * mousedown
   * @param $event
   */
  public chooseBoxStart($event: MouseEvent) {
    this.brushStartTime = new Date().getTime()
    if (this.chooseSwitch) {
      // 清空 拖拽的节点
      // this.draggingNodeItem = []
      // 清空之前选的节点 Ctrl / brush
      DataViewStore.clearDataPreviewImmediately()
      // DataViewStore.changeSelectNode(null)
      // 清空框选的节点 框选的节点 作为 拖拽的节点
      this.chooseBoxNodes = []
      this.graphContent = this.$refs.graphContent as HTMLElement
      // 行列  记录
      this.chooseBoxStartEnd.start.col = Math.floor(
        ($event.x - this.containerPos.x + this.graphContent.scrollLeft) /
          this.gridSizeWidth
      )
      this.chooseBoxStartEnd.start.row = Math.floor(
        ($event.y - this.containerPos.y + this.graphContent.scrollTop) /
          this.gridSizeHeight
      )
      this.chooseBoxStartEnd.end.col = -1
      this.chooseBoxStartEnd.end.row = -1

      // 位置
      this.chooseBoxStartEndPosition.start.x =
        $event.x - this.containerPos.x + this.graphContent.scrollLeft
      this.chooseBoxStartEndPosition.start.y =
        $event.y - this.containerPos.y + this.graphContent.scrollTop

      const that = this
      const mousemoveOfBrush = function (moveEvent: MouseEvent) {
        const { graphContent } = that
        // 位置
        that.chooseBoxStartEndPosition.end.x =
          moveEvent.x - that.containerPos.x + graphContent.scrollLeft
        that.chooseBoxStartEndPosition.end.y =
          moveEvent.y - that.containerPos.y + graphContent.scrollTop
        // 当有一定距离拖动才触发框选
        if (
          !that.chooseBoxShow &&
          (Math.abs(
            that.chooseBoxStartEndPosition.end.x -
              that.chooseBoxStartEndPosition.start.x
          ) > 20 ||
            Math.abs(
              that.chooseBoxStartEndPosition.end.y -
                that.chooseBoxStartEndPosition.start.y
            ) > 20)
        ) {
          // 框选动作框显示
          that.chooseBoxShow = true
        }
        if (that.chooseBoxShow) {
          that.chooseBoxStartEnd.end.col = Math.ceil(
            (moveEvent.x - that.containerPos.x + graphContent.scrollLeft) /
              that.gridSizeWidth
          )
          that.chooseBoxStartEnd.end.row = Math.ceil(
            (moveEvent.y - that.containerPos.y + graphContent.scrollTop) /
              that.gridSizeHeight
          )
        }
      }
      document.addEventListener('mousemove', mousemoveOfBrush)
      const mouseupOfBrush = function () {
        document.removeEventListener('mousemove', mousemoveOfBrush)
        document.removeEventListener('mouseup', mouseupOfBrush)
        // 隐藏动作框
        that.chooseBoxShow = false
        // 初始化 位置
        that.chooseBoxStartEndPosition = {
          start: { x: -3000, y: -3000 },
          end: { x: -3000, y: -3000 },
        }
      }
      document.addEventListener('mouseup', mouseupOfBrush)
    }
  }

  // 不包含 selectNode
  public get multiSelectNodeArray() {
    return DataViewStore.multiSelectNodeArray
  }

  // 定位节点
  public get locateNodeArray() {
    return DataViewStore.locateNodes
  }

  /**
   * 计算是否框选 的节点， 用于样式
   * @param nodeId 节点 Id
   */
  private calcMultiSelectNodeArray(array: any[], nodeId: number) {
    if (array.length > 0) {
      return array.some((item: any) => item.id === nodeId)
    }
    return false
  }

  // =======================  连线相关 start =======================
  // 连接线高亮 [startNodeId-endNodeId]
  private connectionLineHighlight: any = []

  /**
   * 根据节点对连接线信息渲染列表， 左右中点为连接点
   */
  public get getEdgeInfo() {
    // 方法传参
    const parameters: any = {
      nodeHeight: this.nodeHeight,
      nodeWidth: this.nodeWidth,
      gridSizeWidth: this.gridSizeWidth,
      gridSizeHeight: this.gridSizeHeight,
      differenceOfGridWidth: this.differenceOfGridWidth,
      differenceOfGridHeight: this.differenceOfGridHeight,
    }
    return generateEdgeInfo(this.operatorNodes, parameters)
  }

  /**
   * 激活连接线
   * @param edge
   */
  public activeLine(edge: any) {
    const allNode = this.operatorNodes
    const lineHighlightList: any[] = []
    // 初始节点对
    lineHighlightList.push(`${edge.startNodeId}-${edge.endNodeId}`)
    // [] 不过滤
    const upstreamNodePair = recursionDealLine(
      allNode,
      edge.startNodeId,
      'parentId',
      []
    )
    const downstreamNodePair = recursionDealLine(
      allNode,
      edge.endNodeId,
      'childId',
      []
    )
    this.connectionLineHighlight = lineHighlightList
      .concat(upstreamNodePair)
      .concat(downstreamNodePair)
  }

  /**
   * 连接线状态
   * @param endNodeId
   */
  public lineStatus(endNodeId: number) {
    let status = ''
    if (
      this.runStatusMap != null &&
      Object.keys(this.runStatusMap).includes(`${endNodeId}`)
    ) {
      if (this.runStatusMap[`${endNodeId}`].status === 'RUNNING') {
        status = '5,5'
      }
    }
    return status
  }

  // 其他 对外

  /**
   * 关闭日志
   */
  public closeLog() {
    this.$emit('close-log')
  }

  /**
   * 关闭收藏
   */
  public closeFavorite() {
    DataViewStore.setFavoriteStatus(false)
    this.$emit('close-favorite')
  }

  /**
   * 关闭版本快照历史
   */
  public closeShot() {
    this.$emit('close-shot')
    DataViewStore.setOverSaveVisible(false)
  }

  /**
   * 关闭保存快照弹框
   */
  public closeHistory() {
    DataViewStore.setHistoryStatus(false)
    this.$emit('close-history')
  }

  // 可视化对比状态
  private get comparisonStatus() {
    return DataViewStore.comparisonStatus
  }

  /**
   * 右下角缩放控件
   * @param zoom 缩放系数
   */
  private changeScale(zoom: number) {
    this.zoomScale = zoom
    // 根据 初始尺寸进行 放大缩小
    this.nodeHeight = (this.initNodeHeight * zoom) / 100
    this.nodeWidth = (this.initNodeWidth * zoom) / 100
    this.gridSizeWidth = (this.initGridSizeWidth * zoom) / 100
    this.gridSizeHeight = (this.initGridSizeHeight * zoom) / 100
    this.cleanMenuHeight = (this.initMenuHeight * zoom) / 100
    this.cleanMenuWidth = (this.initMenuWidth * zoom) / 100
    this.initGraphSizeByNodes()
  }

  /**
   * $emit 节点组件编辑menu
   */
  public nodeEmitEvent(info: { type: string; value: boolean; nodeId: number }) {
    if (this.isVisitor || this.isLock) {
      return
    }
    if (info.type === 'menu') {
      this.nodeMenuStatus = info.value // 只记录状态 只能开启一个右键菜单
      this.clickedAdd = false
      this.menuOpenNodeId = info.value ? info.nodeId : null
    } else if (info.type === 'name') {
      this.nodeEditNameStatus = info.value // 只记录状态
    } else if (info.type === 'forbidden') {
      this.nodeMenuStatus = false // 点击禁用 启用 所有 dropdown 关闭
      if (info.value) {
        this.bulkUpForbidden(info.nodeId) // 批量启用
      } else {
        this.bulkForbidden(info.nodeId) // 批量禁用
      }
    }
  }

  /**
   * 批量禁用
   */
  public async bulkForbidden(nodeId: number) {
    let batchNodes: string = String(nodeId)
    // 当前右键节点是否在所选的节点中，包含-走批量禁用， 否则操作当前单个节点
    if (this.multiWithSelectNodes.some((item: any) => item.id === nodeId)) {
      batchNodes = this.multiWithSelectNodes
        .map((node: any) => node.id)
        .toString()
    }
    const parameters = {
      ids: batchNodes,
      pipelineId: this.pipelineId,
      projectId: this.projectId,
    }
    const response: any = await batchForbidden({ data: parameters })
    if (response.data.code === 100) {
      this.updatePipeline()
      this.$message.success('禁用节点成功 !', 0.5)
    } else if (response.data.code === 500) {
      this.$message.warning(response.data.tips)
    }
  }

  /**
   * 批量启用
   */
  public async bulkUpForbidden(nodeId: number) {
    let batchNodes: string = String(nodeId)
    // 当前右键节点是否在所选的节点中，包含-走批量禁用， 否则操作当前单个节点
    if (this.multiWithSelectNodes.some((item: any) => item.id === nodeId)) {
      batchNodes = this.multiWithSelectNodes
        .map((node: any) => node.id)
        .toString()
    }
    const parameters = {
      ids: batchNodes,
      pipelineId: this.pipelineId,
      projectId: this.projectId,
    }
    const response: any = await batchUnForbidden({ data: parameters })
    if (response.data.code === 100) {
      this.updatePipeline()
      this.$message.success('启用节点成功 !', 0.5)
      // 节点启用执行
      // this.$nextTick(() =>{
      //   this.pipelineNodeRelationshipsChange(info.nodeId)
      // })
    } else if (response.data.code === 500) {
      this.$message.warning(response.data.tips)
    }
  }

  public nodeEdit(node: any) {
    // jupyter 自定义算子
    if (node.type === 9) {
      this.queryJupyterOperator({
        operatorId: node.operatorId,
        taskId: node.id,
        type: node.type,
      })
      return
    }
    this.operatorId = node.operatorId
    this.taskId = node.id
    this.addEditVisible = true
    DataViewStore.setEditOperatorNode(node)
  }

  // ------------------------ 节点复制 --------------------
  /**
   * 节点 复制 (含有批量)
   * ($emit)
   * @param copyNode 复制节点
   */
  public async nodeCopy(copyNode: any) {
    this.nodeMenuStatus = false // 点击复制 所有 dropdown 关闭
    let batchNodes: string = String(copyNode.id)
    // 当前右键节点是否在所选的节点中，包含-走批量禁用， 否则操作当前单个节点
    if (
      this.multiWithSelectNodes.some((item: any) => item.id === copyNode.id)
    ) {
      batchNodes = this.multiWithSelectNodes
        .map((node: any) => node.id)
        .toString()
    }
    const parameters = {
      ids: batchNodes,
      pipelineId: this.pipelineId,
      projectId: this.projectId,
    }
    const response: any = await batchCopy({ data: parameters })
    if (response.data.code === 100) {
      this.updatePipeline()
      this.$message.success('复制节点成功 !', 0.5)
    } else if (response.data.code === 500) {
      this.$message.warning(response.data.tips)
    }
  }

  // ------------------------ 节点删除 --------------------

  public deleteNodes: any[] = [] // 将要删除的节点（不一定是当前节点）
  public deleteConfirmVisible: boolean = false // 确认删除节点弹窗
  public isExists: boolean = false // 右键删除的当前节点是否在多选的节点中
  public get multiWithSelectNodes() {
    return DataViewStore.multiWithSelectNodes
  }

  /**
   * 删除节点
   */
  public deleteNode(node: any) {
    this.nodeMenuStatus = false // 点击删除 所有 dropdown 关闭
    // 当前右键节点是否在所选的节点中， 如果包含则是批量操作， 否则删除当前节点
    this.isExists = this.multiWithSelectNodes.some(
      (item: any) => item.id === node.id
    )
    // 存在走批量删除
    if (this.isExists) {
      this.deleteNodes = this.multiWithSelectNodes
    } else {
      this.deleteNodes = [node]
    }
    // 确认弹窗
    if (DataViewStore.pipelineDeleteConfirm) {
      this.deleteConfirmVisible = true
    } else {
      this.confirmDeleteNode()
    }
  }

  /**
   * 确认删除节点
   */
  public async confirmDeleteNode() {
    const nodeIds: string = this.deleteNodes
      .map((node: any) => node.id)
      .toString()
    const parameters = {
      ids: nodeIds,
      pipelineId: this.pipelineId,
      projectId: this.projectId,
    }
    const response: any = await batchDelete({ data: parameters })
    if (response.data.code === 100) {
      // 如果 true 说明是 所选批量删除， 成功后清空所选
      if (this.isExists) {
        DataViewStore.clearMultiSelectNodeArray()
      }
      DataViewStore.deleteNodeUpdateSelectNode(this.deleteNodes)
      // 删除节点更新pipeline所有节点状态
      this.updatePipeline()
      this.$message.success('删除节点成功 !', 0.5)
      this.deleteConfirmVisible = false
    } else if (response.data.code === 500) {
      this.$message.warning(response.data.tips)
    }
  }

  /**
   * 取消删除
   */
  public cancelDelete() {
    this.deleteConfirmVisible = false
  }

  // 是否是 mac
  private get isMac() {
    const { userAgent } = window.navigator
    return userAgent.includes('Mac OS X')
  }

  /**
   * 是否按住ctrl多选模式：win下按住ctrl键，mac下按住command键
   */
  private isCtrlSelect(event: MouseEvent) {
    if (this.isMac) {
      return event.metaKey
    }
    return event.ctrlKey
  }
}
</script>

<style lang="less" scoped>
@import '@/components/studio/data/pipline/pipeline';
</style>
